package org.tools.wstt.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.SocketTimeoutException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

/**
 * Servlet implementation class SendRequestsServlet.
 */
public class FetchUrlServlet extends AHttpServlet {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;

	/**
	 * Do get.
	 * 
	 * @param pRequest
	 *            the request
	 * @param pResponse
	 *            the response
	 * @throws ServletException
	 *             the servlet exception
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest pRequest,
			HttpServletResponse pResponse) throws ServletException, IOException {

		if (!StringUtils.isEmpty(pRequest.getParameter("url"))) {
			String vUrl = pRequest.getParameter("url");
			String vMethod = pRequest.getParameter("method");
			String vContent = pRequest.getParameter("httpContent");
			String vHeaders = pRequest.getParameter("httpHeaders");

			String vProxyHost = "";
			int vProxyPort = 0;
			if(!StringUtils.isEmpty(pRequest.getParameter("useProxy"))) {
				vProxyHost = pRequest.getParameter("proxyHost");
				vProxyPort = Integer.valueOf(pRequest.getParameter("proxyPort")).intValue();
			}
			
			// timeout
			Long vTimeout = APP_PROBE_DEF_TIMEOUT_MS;
			if (!StringUtils.isEmpty(pRequest.getParameter("timeout"))) {
				vTimeout = Long.valueOf(pRequest.getParameter("timeout"));
			}

			// call probing
			Map vReqResultMap = sendRequest(vUrl, vMethod, vContent, vHeaders,
					vTimeout, vProxyHost, vProxyPort);

			// save request result
			List vList = (List) mContext.getAttribute(APP_REQUEST_RESULTS);
			if (vList.size() >= APP_REQUEST_REMIND_CALLS) {
				vList.remove(0);
			}
			vList.add(vReqResultMap);

			// make output
			pResponse.sendRedirect(URI_TOOLS + "/fetch_url");

		} else {
			pResponse.sendError(HttpServletResponse.SC_PRECONDITION_FAILED,
					"host and port parameters are required.");
		}
	}

	/**
	 * Do post.
	 *
	 * @param pRequest the request
	 * @param pResponse the response
	 * @throws ServletException the servlet exception
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
	 * javax.servlet.http.HttpServletResponse)
	 */
	protected void doPost(HttpServletRequest pRequest,
			HttpServletResponse pResponse) throws ServletException, IOException {
		this.doGet(pRequest, pResponse);
	}

	/**
	 * Send Request logic.
	 *
	 * @param pUrl the url
	 * @param pMethod the method
	 * @param pInputContent the input content
	 * @param pInputHeaders the input headers
	 * @param pTimeout the timeout
	 * @param pProxyHost the proxy host
	 * @param pProxyPort the proxy port
	 * @return the status
	 */
	protected Map sendRequest(String pUrl, String pMethod,
			String pInputContent, String pInputHeaders, Long pTimeout, String pProxyHost, int pProxyPort) {
		int vStatusCode = APP_PROBE_STATUT_KO;
		long vElapsedTime = -1;
		Map vOutputHeaders = new HashMap();
		String vOutputContent = "";

		try {
			long vStart = System.currentTimeMillis();
			log("Connect to " + pUrl + " - timeout : " + pTimeout + "ms");
			java.net.URL vUrl = new java.net.URL(pUrl);
			HttpURLConnection vConnection = null;
			if(!StringUtils.isEmpty(pProxyHost)) {
				Proxy vProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(pProxyHost,pProxyPort));
				vConnection = (HttpURLConnection) vUrl.openConnection(vProxy);
			}
			else {
				vConnection = (HttpURLConnection) vUrl.openConnection();
			}
			vConnection.setAllowUserInteraction(false);

			// Set method
			vConnection = (HttpURLConnection) vConnection;
			vConnection.setRequestMethod(pMethod);
			vConnection.setInstanceFollowRedirects(false);

			vConnection.setDoInput(true);

			// TODO : process request headers
			addHeaders(vConnection, pInputHeaders);
			
			// use request content if any
			if (!StringUtils.isEmpty(pInputContent)) {
				vConnection.setDoOutput(true);
				vConnection.getOutputStream().write(pInputContent.getBytes());
			}

			// Connect
			vConnection.connect();

			vElapsedTime = System.currentTimeMillis() - vStart;

			// handler status codes etc.
			mLogger.info("response = " + vConnection.getResponseCode());
			vStatusCode = vConnection.getResponseCode();

			InputStream vRequestResIn = vConnection.getErrorStream();
			if (vRequestResIn == null) {
				try {
					vRequestResIn = vConnection.getInputStream();
				} catch (Exception vE) {
					mLogger.info("stream", vE);
					vRequestResIn = vConnection.getErrorStream();
				}
			}
			// TODO : process response content
			StringWriter writer = new StringWriter();
			IOUtils.copy(vRequestResIn, writer);
			vOutputContent = writer.toString();

			// get response headers
			int vH = 0;
			String vHdr = vConnection.getHeaderFieldKey(vH);
			String vVal = vConnection.getHeaderField(vH);
			while (vHdr != null || vVal != null) {
				String lhdr = vHdr != null ? vHdr.toLowerCase() : null;
				if (vHdr != null && vVal != null)
					vOutputHeaders.put(vHdr, vVal);

				mLogger.info("res " + vHdr + ": " + vVal);
				vH++;
				vHdr = vConnection.getHeaderFieldKey(vH);
				vVal = vConnection.getHeaderField(vH);
			}

			log("Connecting ended - time elapsed : " + vElapsedTime + "ms");
		} catch (MalformedURLException vE) {
			logError("Bad URL : " + vE.getMessage(), vE);
		} catch (SocketTimeoutException vE) {
			logError("Error Timeout : " + vE.getMessage(), vE);
		} catch (ConnectException vE) {
			if ("Connection refused: connect".equals(vE.getMessage())) {
				logError("Connection refused : " + vE.getMessage(), vE);
				vStatusCode = APP_PROBE_STATUT_NO_SERVICE;
			} else {
				logError("Error Connecting : " + vE.getMessage(), vE);
			}
		} catch (IOException vE) {
			if ("already connected".equals(vE.getMessage())) {
				logError("Already connected : " + vE.getMessage(), vE);
				vStatusCode = APP_PROBE_STATUT_OK;
			} else {
				logError("Erreur : " + vE.getMessage(), vE);
			}
		}
		Map vResults = new HashMap();
		vResults.put("status", new Integer(vStatusCode));
		vResults.put("date", GregorianCalendar.getInstance().getTime());
		vResults.put("url", pUrl);
		vResults.put("method", pMethod);
		vResults.put("inputContent", pInputContent);
		vResults.put("inputHeaders", pInputHeaders);
		vResults.put("outputHeaders", vOutputHeaders);
		vResults.put("outputContent", vOutputContent);
		vResults.put("time", "" + vElapsedTime);
		return vResults;
	}

	/**
	 * Adds the headers.
	 *
	 * @param pConnection the connection
	 * @param pHeaders the headers
	 */
	protected void addHeaders(HttpURLConnection pConnection, String pHeaders) {
		if (!StringUtils.isEmpty(pHeaders) && pHeaders.indexOf(":") != -1) {
			String[] vHeaders = pHeaders.split("\n");
			for (int i = 0; i < vHeaders.length; i++) {
				String[] vCutHeader = vHeaders[i].split(":", 2);
				if (vCutHeader.length > 1) {
					log("Header to set : [" + vCutHeader[0] + "]=["
							+ vCutHeader[1] + "]");
					pConnection
							.addRequestProperty(vCutHeader[0], vCutHeader[1]);
				}
			}
		}
	}
	
	/**
	 * Generate output.
	 *
	 * @param pRequest the request
	 * @param pResponse the response
	 * @param pProbeResult the probe result
	 * @return true, if successful
	 * @throws ServletException the servlet exception
	 * @throws IOException Signals that an I/O exception has occurred.
	 */
	protected boolean generateOutput(HttpServletRequest pRequest,
			HttpServletResponse pResponse, Map pProbeResult)
			throws ServletException, IOException {
		boolean vOutputDone = false;
		
		// format alternatives ?
		if (!StringUtils.isEmpty(pRequest.getParameter("alt"))) {
			StringBuffer vResponseBuff = new StringBuffer();
			String vContentType = "text/plain";
			if ("json".equals(pRequest.getParameter("alt"))) {
				boolean vCallback = false;
				vContentType = "application/json";
				// JSONP callback ?
				if (!StringUtils.isEmpty(pRequest.getParameter("callback"))) {
					vCallback = true;
					vContentType = "text/javascript";
					vResponseBuff.append(pRequest.getParameter("callback"));
					vResponseBuff.append("(");
				}
				vResponseBuff.append("{\"status\": \"");
				vResponseBuff.append(pProbeResult.get("status"));
				vResponseBuff.append("\"}");
				if (vCallback) {
					vResponseBuff.append(")");
				}
			} else {
				// just as text
				vResponseBuff.append("status=");
				vResponseBuff.append(pProbeResult.get("status"));
			}
			pResponse.setContentType(vContentType);
			pResponse.getOutputStream().write(
					vResponseBuff.toString().getBytes());
			vOutputDone = true;
		}
		return vOutputDone;
	}
}
